package BackgammonBot.Algorithmus;

import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;

import BackgammonBot.GameLogic.*;
import BackgammonBot.Interfaces.*;
import BackgammonBot.Players.*;

public class AlphaBetaAlgoMultiThread extends Thread
{
	private BackgammonLogic gameLogic;
	private PlayerBase aggressivePlayer = new AggressivePlayer();
	private PlayerBase normalPlayer = new NormalPlayer();
	
	private ArrayList<int[]> diceList;

	
	private char maxPlayer;
	private char minPlayer;
	private int depth;
	
	private volatile ArrayList<int[]> move;
	private AtomicInteger moveFittness;
 	
	public AlphaBetaAlgoMultiThread(int depth
			, BackgammonLogic gammonLogic) 
	{
		this.gameLogic = gammonLogic;
		this.depth = depth;
		this.diceList = this.generateAllDiceThrows();
		this.maxPlayer = gammonLogic.getInitialPlayer();
		this.minPlayer = (this.maxPlayer == 'w') ? 'b':'w';
	}
	
	public void run() 
	{
		move = new ArrayList<int[]>();
		
		int alpha = Integer.MIN_VALUE;
		int beta = Integer.MAX_VALUE;
		
		this.moveFittness.set(this.max(this.depth, alpha, beta));
	}

	public int max(int depth
			, int alpha
			, int beta) 
	{
		if (depth == 0)
		{
			return gameLogic.calcFittness(this.maxPlayer, this.aggressivePlayer);
		}
		
		int maxValue = alpha;		
		
		// if init max
		if (depth == this.depth) 
		{
			ArrayList<int[]> diceThrows = new ArrayList<int[]>();
			diceThrows.add(gameLogic.getInitialDice());
			
			// Is the initial throw a double, then add only 1,1 or 2,2 ... once.
			// Otherwise add for a throw 1,2 also 2,1
			if (gameLogic.getInitialDice()[1] != gameLogic.getInitialDice()[0])
			{
				int[] diceThrow = new int[2];
				diceThrow[0] = gameLogic.getInitialDice()[1];
				diceThrow[1] = gameLogic.getInitialDice()[0];
				
				diceThrows.add(diceThrow);
				
				maxValue = maxHelper(depth, beta, maxValue, diceThrows);
			}
			else 
			{
				maxValue = maxHelper(depth, beta, maxValue, diceThrows);
			}
			
			
		} 
		else 
		{
			maxValue = maxHelperFourMoves(depth, beta, maxValue, gameLogic.getInitialDice()[0]);
		};
		
		
		return maxValue;
	}

	private int maxHelper(int depth, int beta, int maxValue,
			ArrayList<int[]> diceThrows) 
	{
		for (int i = 0; i < diceThrows.size(); i++) 
		{
			if (diceThrows.get(i)[0] == diceThrows.get(i)[1])
			{
				int tmp_maxValue = maxHelperFourMoves(depth, beta, maxValue, diceThrows.get(i)[0]);
				
				if (tmp_maxValue > maxValue)
					maxValue = tmp_maxValue;
				
				continue;
			}
			
			ArrayList<int[]> possibleHopsFirstDice = this.gameLogic.getPossibleHops(diceThrows.get(i)[0], this.maxPlayer);
			
			// check if no hop is possible, then continue
			if (possibleHopsFirstDice.size() == 0)
			{
				continue;
			}
			
			// Set first dice
			for (int[] firstHop : possibleHopsFirstDice) 
			{			
				this.gameLogic.doMove(firstHop, this.maxPlayer);
				
				ArrayList<int[]> possibleHopsSecondDice = this.gameLogic.getPossibleHops(diceThrows.get(i)[1], this.maxPlayer);
				
				// check if no hop is possible, then continue
				if (possibleHopsSecondDice.size() == 0)
				{
					int minValue = min(depth-1, maxValue, beta);
					
					this.gameLogic.undoMove();
					
					if (minValue > maxValue)
					{
						maxValue = minValue;
						if (maxValue >= beta)
						{
							break;
						}
						
						if (depth == this.depth) 
						{
							move.clear();
							move.add(firstHop);
						}
					}
					
					continue;
				}
				
				// Set second dice after first dice move is done
				for (int[] secondHop : possibleHopsSecondDice) 
				{				
					this.gameLogic.doMove(secondHop, this.maxPlayer);
					
					int minValue = min(depth-1, maxValue, beta);
					
					this.gameLogic.undoMove();
					
					if (minValue > maxValue)
					{
						maxValue = minValue;
						if (maxValue >= beta)
						{
							break;
						}
						
						if (depth == this.depth) 
						{
							move.clear();
							move.add(firstHop);
							move.add(secondHop);
						}
					}
				}
				
				this.gameLogic.undoMove();
				
			}
		}
		return maxValue;
	}
	
	private int maxHelperFourMoves(int depth, int beta, int maxValue,
			int dice) 
	{
		ArrayList<int[]> possibleHopsFirstDice = this.gameLogic.getPossibleHops(dice, this.maxPlayer);
		
		// check if no hop is possible, then continue
		if (possibleHopsFirstDice.size() == 0)
		{
			return Integer.MIN_VALUE;
		}
		
		for (int[] firstHop : possibleHopsFirstDice) 
		{	
			this.gameLogic.doMove(firstHop, this.maxPlayer);
								
			ArrayList<int[]> possibleHopsSecondDice = this.gameLogic.getPossibleHops(dice, this.maxPlayer);
			
			// check if no hop is possible, then continue
			if (possibleHopsSecondDice.size() == 0)
			{
				int minValue = min(depth-1, maxValue, beta);
				minValue = (int) Math.floor(minValue / 6);
				
				this.gameLogic.undoMove();
				
				if (minValue > maxValue)
				{
					maxValue = minValue;
					if (maxValue >= beta)
					{
						break;
					}
					
					if (depth == this.depth) 
					{
						move.clear();
						move.add(firstHop);
					}
				}
				
				continue;
			}
			
			for (int[] secondHop : possibleHopsSecondDice) 
			{				
				this.gameLogic.doMove(secondHop, this.maxPlayer);

				ArrayList<int[]> possibleHopsThirdDice = this.gameLogic.getPossibleHops(dice, this.maxPlayer);
				
				// check if no hop is possible, then continue
				if (possibleHopsThirdDice.size() == 0)
				{
					int minValue = min(depth-1, maxValue, beta);
					minValue = (int) Math.floor(minValue / 6);
					
					this.gameLogic.undoMove();
					
					if (minValue > maxValue)
					{
						maxValue = minValue;
						if (maxValue >= beta)
						{
							break;
						}
						
						if (depth == this.depth) 
						{
							move.clear();
							move.add(firstHop);
							move.add(secondHop);
						}
					}
					
					continue;
				}
				
				
				for (int[] thirdHop : possibleHopsThirdDice) 
				{					
					this.gameLogic.doMove(thirdHop, this.maxPlayer);

					ArrayList<int[]> possibleHopsFourthDice = this.gameLogic.getPossibleHops(dice, this.maxPlayer);
					
					// check if no hop is possible, then continue
					if (possibleHopsFourthDice.size() == 0)
					{
						int minValue = min(depth-1, maxValue, beta);
						minValue = (int) Math.floor(minValue / 6);
						
						this.gameLogic.undoMove();
						
						if (minValue > maxValue)
						{
							maxValue = minValue;
							if (maxValue >= beta)
							{
								break;
							}
							
							if (depth == this.depth) 
							{
								move.clear();
								move.add(firstHop);
								move.add(secondHop);
								move.add(thirdHop);
							}
						}
						
						continue;
					}
					
					for (int[] fourthHop : possibleHopsFourthDice) 
					{	
						this.gameLogic.doMove(fourthHop, this.maxPlayer);
				
						int minValue = min(depth-1, maxValue, beta);
						minValue = (int) Math.floor(minValue / 6);
						
						this.gameLogic.undoMove();
						
						if (minValue > maxValue)
						{
							maxValue = minValue;
							if (maxValue >= beta)
							{
								break;
							}
							
							if (depth == this.depth) 
							{
								move.clear();
								move.add(firstHop);
								move.add(secondHop);
								move.add(thirdHop);
								move.add(fourthHop);
							}
						}
					}
					this.gameLogic.undoMove();
				}
				this.gameLogic.undoMove();
			}
			this.gameLogic.undoMove();
		}
		return maxValue;
	}
	
	
	
	public int min(int depth, int alpha, int beta) 
	{
		if (depth == 0)
		{
			return gameLogic.calcFittness(this.maxPlayer, this.normalPlayer);
		}
		
		int minValue = beta;	
		minValue = minHelper(depth, alpha, minValue, this.diceList);
		return minValue;
	}
	
	private int minHelper(int depth, int alpha, int minValue,
			ArrayList<int[]> diceThrows)
	{
		for (int i = 0; i < diceThrows.size(); i++) 
		{
			if (diceThrows.get(i)[0] == diceThrows.get(i)[1])
			{
				int tmp_minValue = minHelperFourMoves(depth, alpha, minValue, diceThrows.get(i)[0]);
				
				if (tmp_minValue < minValue)
					minValue = tmp_minValue;
				
				continue;
			}
			
			ArrayList<int[]> possibleHopsFirstDice = this.gameLogic.getPossibleHops(diceThrows.get(i)[0], this.minPlayer);
			
			// check if no hop is possible, then continue
			if (possibleHopsFirstDice.size() == 0)
			{				
				continue;
			}			
			
			for (int[] firstHop : possibleHopsFirstDice) 
			{				
				this.gameLogic.doMove(firstHop, this.minPlayer);
									
				ArrayList<int[]> possibleHopsSecondDice = this.gameLogic.getPossibleHops(diceThrows.get(i)[1], this.minPlayer);
				
				// check if no hop is possible, then continue
				if (possibleHopsSecondDice.size() == 0)
				{
					int maxValue = max(depth-1, alpha, minValue);
					
					this.gameLogic.undoMove();
					
					if (maxValue < minValue)
					{
						minValue = maxValue;
						if (minValue <= alpha)
						{
							break;
						}
					}
					
					continue;
				}
				
				for (int[] secondHop : possibleHopsSecondDice) 
				{					
					this.gameLogic.doMove(secondHop, this.minPlayer);
					
					// Mult 5 bacause the chance to throw no double is 5 times bigger than throw a double
					int maxValue = max(depth-1, alpha, minValue);
					
					this.gameLogic.undoMove();
					
					if (maxValue < minValue)
					{
						minValue = maxValue;
						if (minValue <= alpha)
						{
							break;
						}
					}
				}
				
				this.gameLogic.undoMove();
				
			}
		}
		return minValue;
	}
	
	private int minHelperFourMoves(int depth, int alpha, int minValue,
			int dice) 
	{
		ArrayList<int[]> possibleHopsFirstDice = this.gameLogic.getPossibleHops(dice, this.minPlayer);
		
		// check if no hop is possible, then continue
		if (possibleHopsFirstDice.size() == 0)
		{
			return Integer.MAX_VALUE;
		}
		
		for (int[] firstHop : possibleHopsFirstDice) 
		{
			this.gameLogic.doMove(firstHop, this.minPlayer);
								
			ArrayList<int[]> possibleHopsSecondDice = this.gameLogic.getPossibleHops(dice, this.minPlayer);
			
			// check if no hop is possible, then continue
			if (possibleHopsSecondDice.size() == 0)
			{
				// *6, bacause the chance to throw no double is 5 times bigger than throw a double
				int maxValue = max(depth-1, alpha, minValue);
				maxValue = (int) Math.floor(maxValue * 6);
				
				this.gameLogic.undoMove();
				
				if (maxValue < minValue)
				{
					minValue = maxValue;
					if (minValue <= alpha)
					{
						break;
					}
				}
				
				continue;
			}
			
			for (int[] secondHop : possibleHopsSecondDice) 
			{				
				this.gameLogic.doMove(secondHop, this.minPlayer);
					
				ArrayList<int[]> possibleHopsThirdDice = this.gameLogic.getPossibleHops(dice, this.minPlayer);
				
				// check if no hop is possible, then continue
				if (possibleHopsThirdDice.size() == 0)
				{
					// *6, bacause the chance to throw no double is 5 times bigger than throw a double
					int maxValue = max(depth-1, alpha, minValue);
					maxValue = (int) Math.floor(maxValue * 6);
					
					this.gameLogic.undoMove();
					
					if (maxValue < minValue)
					{
						minValue = maxValue;
						if (minValue <= alpha)
						{
							break;
						}
					}
					
					continue;
				}
				
				for (int[] thirdHop : possibleHopsThirdDice) 
				{	
					this.gameLogic.doMove(thirdHop, this.minPlayer);

					ArrayList<int[]> possibleHopsFourthDice = this.gameLogic.getPossibleHops(dice, this.minPlayer);
					
					// check if no hop is possible, then continue
					if (possibleHopsFourthDice.size() == 0)
					{
						// *6, bacause the chance to throw no double is 5 times bigger than throw a double
						int maxValue = max(depth-1, alpha, minValue);
						maxValue = (int) Math.floor(maxValue * 6);
						
						this.gameLogic.undoMove();
						
						if (maxValue < minValue)
						{
							minValue = maxValue;
							if (minValue <= alpha)
							{
								break;
							}
						}
						
						continue;
					}
					
					for (int[] fourthHop : possibleHopsFourthDice) 
					{
						this.gameLogic.doMove(fourthHop, this.minPlayer);
				
						// *6, bacause the chance to throw no double is 5 times bigger than throw a double
						int maxValue = max(depth-1, alpha, minValue);
						maxValue = (int) Math.floor(maxValue * 6);
						
						this.gameLogic.undoMove();
						
						if (maxValue < minValue)
						{
							minValue = maxValue;
							if (minValue <= alpha)
							{
								break;
							}
						}
					}
					this.gameLogic.undoMove();
				}
				this.gameLogic.undoMove();
			}
			this.gameLogic.undoMove();
		}
		return minValue;
	}
	
	
	
	private ArrayList<int[]> generateAllDiceThrows() {
		ArrayList<int[]> diceList = new ArrayList<int[]>();

		for (int i = 1; i < 7; i++) {
			for (int j = i + 1; j < 7; j++) {
				int[] diceThrow = new int[2];
				diceThrow[0] = i;
				diceThrow[1] = j;
				
				int[] diceThrow2 = new int[2];
				diceThrow2[0] = j;
				diceThrow2[1] = i;
				
				diceList.add(diceThrow);
				diceList.add(diceThrow2);
			}
		}
		for (int i = 1; i < 7; i++)
		{
			int[] doubles = new int[2];
			doubles[0] = doubles[1] = i;
			diceList.add(doubles);
		}
		return diceList;
	}

	public ArrayList<int[]> getMove() 
	{
		return move;
	}

	public int getMoveFittness() 
	{
		return moveFittness.get();
	}

	public char getPlayer() 
	{
		return maxPlayer;
	}
}
